package ua.yandex.collections;

/**
 *
 * @author WslF
 */
public class MyLinkedList implements MyList {

    public static class ListNode {

        private Object val;
        private ListNode prev;
        private ListNode next;

        public ListNode(Object val) {
            this(val, null, null);
        }

        public ListNode(Object val, ListNode prev, ListNode next) {
            this.val = val;
            this.next = next;
            this.prev = prev;
        }
    }

    private int size;
    private ListNode first;
    private ListNode last;

    public MyLinkedList() {
        size = 0;
        first = null;
        last = null;
    }

    @Override
    public void add(Object e) {
        addLast(e);
    }

    @Override
    public void add(int index, Object e) {
        if (index == size) { //хотим добавить в конец
            addLast(e);
            return;
        }

        checkIndex(index);
        if (index == 0) { //хотим добавить в начало
            addFirst(e);
        } else {
            ListNode preNode = getNode(index - 1);
            addNodeAfter(preNode, e);
            size++;
        }
    }

    @Override
    public void addAll(Object[] c) {
        for (Object obj : c) {
            add(obj);
        }
    }

    @Override
    public void addAll(int index, Object[] c) {
        if (index == size) { //хотим добавить в конец
            addAll(c);
            return;
        }

        checkIndex(index);

        if (c.length == 0) {
            return;
        }

        ListNode node;
        add(index, c[0]);
        if (index == 0) {
            node = first;
        } else {
            node = getNode(index);
        }

        for (int i = 1; i < c.length; i++, node = node.next) {
            addNodeAfter(node, c[i]);
        }
        size += c.length;
    }

    @Override
    public Object get(int index) {
        return getNode(index).val;
    }

    @Override
    public Object remove(int index) {
        checkIndex(index);
        if (index == 0) {
            return removeFirst();
        }
        if (index == size - 1) {
            return removeLast();
        }

        ListNode node = getNode(index);
        node.prev.next = node.next;
        node.next.prev = node.prev;
        size--;
        return node.val;
    }

    @Override
    public void set(int index, Object e) {
        checkIndex(index);
        ListNode node = getNode(index);
        node.val = e;
    }

    @Override
    public int indexOf(Object o) {
        ListNode node = first;
        for (int i = 0; i < size; i++, node = node.next) {
            if (node.val.equals(o)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        first = null;
        last = null;
        size = 0;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public Object[] toArray() {
        Object[] array = new Object[size];
        ListNode node = first;
        for (int i = 0; i < size; i++, node = node.next) {
            array[i] = node.val;
        }
        return array;
    }

    @Override
    public String toString() {
        if (size == 0) {
            return "";
        }
        StringBuilder ans = new StringBuilder(first.val.toString());
        for (ListNode node = first.next; node != null; node = node.next) {
            ans.append(", " + node.val.toString());
        }
        return ans.toString();
    }

    public void addFirst(Object e) {
        if (isEmpty()) {
            first = new ListNode(e);
            last = first;
        } else {
            ListNode head = new ListNode(e, null, first);
            first.prev = head;
            first = head;
        }
        size++;
    }

    public void addLast(Object e) {
        if (isEmpty()) {
            first = new ListNode(e);
            last = first;
        } else {
            last = addNodeAfter(last, e);
        }
        size++;
    }

    public Object getFirst() {
        checkIndex(0);
        return first.val;
    }

    public Object getLast() {
        checkIndex(0);
        return last.val;
    }

    public Object removeFirst() {
        checkIndex(0);
        ListNode node = first;
        first = node.next;
        size--;
        return node.val;
    }

    public Object removeLast() {
        checkIndex(0);
        ListNode node = last;
        last = last.prev;
        last.next = null;
        size--;
        return node.val;
    }

    private boolean checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        return true;
    }

    private ListNode getNode(int index) {
        checkIndex(index);

        ListNode ans;
        if (index <= size / 2) {
            ans = first;
            for (int i = 0; i < index; i++) {
                ans = ans.next;
            }
        } else {
            ans = last;
            for (int i = size - 1; i > index; i--) {
                ans = ans.prev;
            }
        }

        return ans;
    }

    /**
     *
     * @param currentNode вершина, после которой хоти добавить новую
     * @param newNodeVal значение в новой вершине
     * @return новую вершину
     */
    private ListNode addNodeAfter(ListNode currentNode, Object newNodeVal) {
        if (currentNode == null) {
            throw new NullPointerException("You try add node after null node!");
        }
        ListNode newNode
                = new ListNode(newNodeVal, currentNode, currentNode.next);
        currentNode.next = newNode;
        if (newNode.next != null) {
            newNode.next.prev = newNode;
        }
        return newNode;
    }

}
